#include "LogFile.h"
#include "file/AppendFile.h"
#include "thread/process.h"
#include "wrap/timing.h"

#include <array>

namespace _ {

static constexpr time_t ROLL_PERIOD = 24 * 60 * 60;

static constexpr time_t MOMENT(time_t now) {
    return now / ROLL_PERIOD * ROLL_PERIOD;
}

static std::unique_ptr<AppendFile> roll_file(
    const std::string &prefix, const std::string &suffix, bool rename, Wrap::time_t_ now) {
    if (!rename) {
        return std::make_unique<AppendFile>(prefix + suffix);
    }
    struct tm t{};
    Wrap::localtime_r_(&now, &t);
    std::array<char, 1 << 5> buffer{};
    ::strftime(buffer.data(), buffer.size(), "%Y%m%d.%H%M%S", &t);
    return std::make_unique<AppendFile>(
        prefix + buffer.data() + Process::hostname() + "." +
        std::to_string(Process::pid()) + suffix);
}

}

LogFile::LogFile(
    std::size_t rollSize, std::string prefix, std::string suffix, bool rename, bool threadSafe):
    _mutex{},
    _prefix{std::move(prefix)},
    _suffix{std::move(suffix)},
    _file{},
    _startMoment{0},
    _lastRollMoment{0},
    _lastFlushMoment{0},
    _rollSize{rollSize},
    _rename{rename},
    _threadSafe{threadSafe} {
    _roll();
}

LogFile::~LogFile() = default;

const std::string &LogFile::filename() const {
    return _file->filename();
}

void LogFile::append(const char *buffer, std::size_t length) {
    if (!_threadSafe) {
        _append_without_lock(buffer, length);
        return;
    }
    const std::lock_guard guard{_mutex};
    _append_without_lock(buffer, length);
}

void LogFile::flush() {
    if (!_threadSafe) {
        _file->flush();
        return;
    }
    const std::lock_guard guard{_mutex};
    _file->flush();
}


void LogFile::_append_flush() {
    static constexpr time_t FLUSH_PERIOD = 3;
    const auto now = Wrap::time_(nullptr);
    if (_startMoment != _::MOMENT(now)) {
        _roll();
    }
    else if (now - _lastFlushMoment > FLUSH_PERIOD) {
        _lastFlushMoment = now;
        _file->flush();
    }
}

void LogFile::_append_without_lock(const char *buffer, std::size_t length) {
    static constexpr int CHECK_COUNT = 20000;
    _file->append(buffer, length);
    if (_file->written_bytes() > _rollSize) {
        _roll();
        return;
    }
    if (++_count < CHECK_COUNT) {
        return;
    }
    _count = 0;
    _append_flush();
}

void LogFile::_roll() {
    const auto now = Wrap::time_(nullptr);
    if (now <= _lastRollMoment) {
        return;
    }
    _lastRollMoment = now;
    _lastFlushMoment = now;
    _startMoment = _::MOMENT(now);
    _file = _::roll_file(_prefix, _suffix, _rename, now);
}
